package cn.geewallet.util;

import java.io.BufferedReader;
import java.io.ByteArrayOutputStream;
import java.io.FileInputStream;
import java.io.FileNotFoundException;
import java.io.IOException;
import java.io.InputStream;
import java.io.InputStreamReader;
import java.security.KeyFactory;
import java.security.KeyStore;
import java.security.KeyStoreException;
import java.security.NoSuchAlgorithmException;
import java.security.PrivateKey;
import java.security.PublicKey;
import java.security.Signature;
import java.security.UnrecoverableKeyException;
import java.security.cert.CertificateException;
import java.security.cert.CertificateFactory;
import java.security.cert.X509Certificate;
import java.security.interfaces.RSAPrivateKey;
import java.security.interfaces.RSAPublicKey;
import java.security.spec.InvalidKeySpecException;
import java.security.spec.PKCS8EncodedKeySpec;
import java.security.spec.X509EncodedKeySpec;
import java.util.HashMap;
import java.util.Map;

import javax.crypto.Cipher;

import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

import cn.geewallet.exception.GeewalletClientException;
import cn.hutool.core.codec.Base64;
import cn.hutool.core.util.CharsetUtil;

/**
 * RSAUtils - 对RSA 签名&验签/分段加密&分段解密 的包装 签名算法: "SHA1withRSA", 私钥进行签名; 公钥进行验签. 加密算法:
 * "RSA/ECB/PKCS1Padding", 公钥进行加密; 私钥进行解密.
 *
 * [localPrivKey]是自己的私钥, 自己的公钥给通信对方. [peerPubKey]是对方的公钥, 对方的私钥在对方那边. 为了方便, 这里假定双方的密钥长度一致,
 * 签名和加密的规则也一致.
 *
 * 以`Base64Str`结尾的参数表示内容是Base64编码的字符串, 其他情况都是raw字符串.
 */
public class RSAUtils {

	private final static Logger log = LoggerFactory.getLogger(RSAUtils.class);
	
	public static final String KEY_ALGORITHM = "RSA";
	public static final String SIGNATURE_ALGORITHM = "SHA1withRSA";
	public static final String CIPHER_ALGORITHM = "RSA/ECB/PKCS1Padding"; // 加密block需要预留11字节
	public static final int KEYBIT = 2048;
	public static final int RESERVEBYTES = 11;
	private static Map<String, RSAUtils> clientMap = new HashMap<String, RSAUtils>();

	private static PrivateKey localPrivKey;
	private static PublicKey peerPubKey;
	private static RSAUtils rsaUtils;

	public RSAUtils() {

	}

	// 静态工厂方法，供外部调用
	public static RSAUtils getInstance(String privateStr) throws Exception {
		if (rsaUtils == null) {
			synchronized (RSAUtils.class) {
				if (rsaUtils == null) {
					rsaUtils = new RSAUtils();
					localPrivKey = rsaUtils.getPrivateKey(privateStr);
				}
			}
		}
		return rsaUtils;
	}

	// 静态工厂方法，供外部调用
	public static RSAUtils getClientInstance(String key, String publicStr) throws Exception {
		if (clientMap.get(key) == null) {
			RSAUtils rsaUtils = new RSAUtils();
			peerPubKey = rsaUtils.getPublicKey(publicStr);
			return rsaUtils;
		} else {
			return clientMap.get(key);
		}
	}

	// 静态工厂方法，供外部调用
	public static RSAUtils getInstance(String privateStr, String publicStr) throws Exception {
		if (rsaUtils == null) {
			synchronized (RSAUtils.class) {
				if (rsaUtils == null) {
					rsaUtils = new RSAUtils();
					localPrivKey = rsaUtils.getPrivateKey(privateStr);
					peerPubKey = rsaUtils.getPublicKey(publicStr);
				}
			}
		}
		return rsaUtils;
	}

	/**
	 * 初始化自己的私钥,对方的公钥以及密钥长度.
	 *
	 * @param localPrivKeyBase64Str Base64编码的私钥,PKCS#8编码. (去掉pem文件中的头尾标识)
	 * @param peerPubKeyBase64Str Base64编码的公钥. (去掉pem文件中的头尾标识)
	 * @param keysize 密钥长度, 一般2048
	 */
	public void initKey(String localPrivKeyBase64Str, String peerPubKeyBase64Str, int keysize) throws Exception {
		try {
			localPrivKey = getPrivateKey(localPrivKeyBase64Str);
			peerPubKey = getPublicKey(peerPubKeyBase64Str);
		} catch (InvalidKeySpecException e) {
			e.printStackTrace();
		}
	}

	/**
	 * 初始化自己的私钥,对方的公钥以及密钥长度.
	 * 
	 * @param localPrivKey
	 * @param peerPubKey
	 */
	public void initKey(PrivateKey localPrivKey, PublicKey peerPubKey) {
		RSAUtils.localPrivKey = localPrivKey;
		RSAUtils.peerPubKey = peerPubKey;
	}

	/**
	 * 从文件中输入流中加载公钥
	 *
	 * @param in 公钥输入流
	 * @throws Exception 加载公钥时产生的异常
	 */
	public RSAPublicKey getPublicKey(InputStream in) throws Exception {
		BufferedReader br = new BufferedReader(new InputStreamReader(in));
		try {
			String readLine = null;
			StringBuilder sb = new StringBuilder();
			while ((readLine = br.readLine()) != null) {
				if (readLine.charAt(0) == '-') {
					continue;
				} else {
					sb.append(readLine);
					sb.append('\r');
				}
			}
			return getPublicKey(sb.toString());
		} catch (IOException e) {
			throw new Exception("公钥数据流读取错误");
		} catch (NullPointerException e) {
			throw new Exception("公钥输入流为空");
		} finally {
			try {
				if (br != null) {
					br.close();
				}
			} catch (Exception e) {
				throw new Exception("关闭输入缓存流出错");
			}

			try {
				if (in != null) {
					in.close();
				}
			} catch (Exception e) {
				throw new Exception("关闭输入流出错");
			}
		}
	}

	/**
	 * 从字符串中加载公钥 公钥数据字符串为：pem文件中去掉第一行的“-----BEGIN *** KEY-----”和最后一行的“-----END *** KEY-----的内容
	 *
	 * @param publicKeyStr 公钥数据字符串
	 * @throws Exception 加载公钥时产生的异常
	 */
	public RSAPublicKey getPublicKey(String publicKeyStr) throws Exception {
		try {
			byte[] buffer = Base64.decode(publicKeyStr.getBytes());
			KeyFactory keyFactory = KeyFactory.getInstance("RSA");
			X509EncodedKeySpec keySpec = new X509EncodedKeySpec(buffer);
			RSAPublicKey publicKey = (RSAPublicKey) keyFactory.generatePublic(keySpec);
			return publicKey;
		} catch (NoSuchAlgorithmException e) {
			throw new Exception("无此算法");
		} catch (InvalidKeySpecException e) {
			throw new Exception("公钥非法");
		} catch (NullPointerException e) {
			throw new Exception("公钥数据为空");
		}
	}

	/**
	 * 从文件中加载私钥
	 *
	 * @param keyFileName 私钥文件名
	 * @return 是否成功
	 * @throws Exception
	 */
	public RSAPrivateKey getPrivateKey(InputStream in) throws Exception {
		BufferedReader br = new BufferedReader(new InputStreamReader(in));
		try {
			String readLine = null;
			StringBuilder sb = new StringBuilder();
			while ((readLine = br.readLine()) != null) {
				if (readLine.charAt(0) == '-') {
					continue;
				} else {
					sb.append(readLine);
					sb.append('\r');
				}
			}
			return getPrivateKey(sb.toString());
		} catch (IOException e) {
			throw new Exception("私钥数据读取错误");
		} catch (NullPointerException e) {
			throw new Exception("私钥输入流为空");
		} finally {
			try {
				if (br != null) {
					br.close();
				}
			} catch (Exception e) {
				throw new Exception("关闭输入缓存流出错");
			}

			try {
				if (in != null) {
					in.close();
				}
			} catch (Exception e) {
				throw new Exception("关闭输入流出错");
			}
		}
	}

	/**
	 * 从字符串中加载私钥 私钥数据字符串：pem文件中去掉第一行的“-----BEGIN *** KEY-----”和最后一行的“-----END *** KEY-----的内容
	 *
	 * @param privateKeyStr 私钥数据字符串
	 * @throws Exception 加载私钥时产生的异常
	 */
	public RSAPrivateKey getPrivateKey(String privateKeyStr) throws Exception {
		try {
			byte[] buffer = Base64.decode(privateKeyStr.getBytes());
			PKCS8EncodedKeySpec keySpec = new PKCS8EncodedKeySpec(buffer);
			KeyFactory keyFactory = KeyFactory.getInstance("RSA");
			RSAPrivateKey privateKey = (RSAPrivateKey) keyFactory.generatePrivate(keySpec);
			return privateKey;
		} catch (NoSuchAlgorithmException e) {
			throw new Exception("无此算法");
		} catch (InvalidKeySpecException e) {
			throw new Exception("私钥非法");
		} catch (NullPointerException e) {
			throw new Exception("私钥数据为空");
		}
	}

	/**
	 * RAS加密
	 *
	 * @param peerPubKey 公钥
	 * @param data 待加密信息
	 * @return byte[]
	 * @throws Exception
	 */
	public String encryptRSA(String context) throws Exception {
		byte[] plainBytes = context.getBytes();
		String CIPHER_ALGORITHM = "RSA/ECB/PKCS1Padding"; // 加密block需要预留11字节
		int KEYBIT = 2048;
		int RESERVEBYTES = 11;
		Cipher cipher = Cipher.getInstance(CIPHER_ALGORITHM);
		int decryptBlock = KEYBIT / 8; // 256 bytes
		int encryptBlock = decryptBlock - RESERVEBYTES; // 245 bytes
		// 计算分段加密的block数 (向上取整)
		int nBlock = (plainBytes.length / encryptBlock);
		if ((plainBytes.length % encryptBlock) != 0) { // 余数非0，block数再加1
			nBlock += 1;
		}
		// 输出buffer, 大小为nBlock个decryptBlock
		ByteArrayOutputStream outbuf = new ByteArrayOutputStream(nBlock * decryptBlock);
		cipher.init(Cipher.ENCRYPT_MODE, peerPubKey);
		// cryptedBase64Str =
		// Base64.encodeBase64String(cipher.doFinal(plaintext.getBytes()));
		// 分段加密
		for (int offset = 0; offset < plainBytes.length; offset += encryptBlock) {
			// block大小: encryptBlock 或 剩余字节数
			int inputLen = (plainBytes.length - offset);
			if (inputLen > encryptBlock) {
				inputLen = encryptBlock;
			}
			// 得到分段加密结果
			byte[] encryptedBlock = cipher.doFinal(plainBytes, offset, inputLen);
			// 追加结果到输出buffer中
			outbuf.write(encryptedBlock);
		}
		// 如果是Base64编码，则返回Base64编码后的数组
		return Base64.encode(outbuf.toByteArray());
	}

	/**
	 * RSA解密
	 *
	 * @param localPrivKey 私钥
	 * @param cryptedBytes 待解密信息
	 * @return byte[]
	 * @throws SecretException
	 * @throws Exception
	 */
	public String decryptRSA(String crypte) throws GeewalletClientException {
		try {
			String CIPHER_ALGORITHM = "RSA/ECB/PKCS1Padding"; // 加密block需要预留11字节
			byte[] data = Base64.decode(crypte);
			int KEYBIT = 2048;
			int RESERVEBYTES = 11;
			Cipher cipher = Cipher.getInstance(CIPHER_ALGORITHM);
			int decryptBlock = KEYBIT / 8; // 256 bytes
			int encryptBlock = decryptBlock - RESERVEBYTES; // 245 bytes
			// 计算分段解密的block数 (理论上应该能整除)
			int nBlock = (data.length / decryptBlock);
			// 输出buffer, , 大小为nBlock个encryptBlock
			ByteArrayOutputStream outbuf = new ByteArrayOutputStream(nBlock * encryptBlock);
			cipher.init(Cipher.DECRYPT_MODE, localPrivKey);
			// plaintext = new
			// String(cipher.doFinal(Base64.decode(cryptedBase64Str)));
			// 分段解密
			for (int offset = 0; offset < data.length; offset += decryptBlock) {
				// block大小: decryptBlock 或 剩余字节数
				int inputLen = (data.length - offset);
				if (inputLen > decryptBlock) {
					inputLen = decryptBlock;
				}

				// 得到分段解密结果
				byte[] decryptedBlock = cipher.doFinal(data, offset, inputLen);
				// 追加结果到输出buffer中
				outbuf.write(decryptedBlock);
			}
			outbuf.flush();
			outbuf.close();
			return new String(outbuf.toByteArray());
		} catch (Exception e) {
			throw new GeewalletClientException("解密失败");
		}
	}

	/**
	 * RSA签名
	 *
	 * @param localPrivKey 私钥
	 * @param plaintext 需要签名的信息
	 * @return byte[]
	 * @throws Exception
	 */
	public String signRSA(String context, String charset) throws Exception {
		log.info("参与签名内容[{}]", context);
		String SIGNATURE_ALGORITHM = "SHA1withRSA";
		Signature signature = Signature.getInstance(SIGNATURE_ALGORITHM);
		signature.initSign(localPrivKey);
		signature.update(context.getBytes(CharsetUtil.UTF_8));
		// 如果是Base64编码的话，需要对签名后的数组以Base64编码
		return Base64.encode(signature.sign());
	}

	/**
	 * 验签操作
	 *
	 * @param peerPubKey 公钥
	 * @param plainBytes 需要验签的信息
	 * @param signBytes 签名信息
	 * @return boolean
	 */
	public boolean verifyRSA(String context, String sign, String charset) {
		try {
			log.info("签名内容[{}]", context);
			String SIGNATURE_ALGORITHM = "SHA1withRSA";
			Signature signature = Signature.getInstance(SIGNATURE_ALGORITHM);
			signature.initVerify(peerPubKey);
			signature.update(context.getBytes(charset));
			// 如果是Base64编码的话，需要对验签的数组以Base64解码
			return signature.verify(Base64.decode(sign));
		} catch (Exception e) {
			log.error("验证签名失败", e);
		}
		return false;
	}

	/**
	 * 获取 私钥key
	 * 
	 * @param pfxPath
	 * @param pfxPasswd
	 * @return
	 * @throws Exception
	 */
	public PrivateKey getPrivateKeyFromPfx(String pfxPath, String pfxPasswd) throws Exception {
		// 替换为自己的客户端私钥路径
		try {
			FileInputStream fis2 = new FileInputStream(pfxPath);
			KeyStore ks = KeyStore.getInstance("PKCS12");
			char[] keypwd = pfxPasswd.toCharArray(); // 证书密码
			ks.load(fis2, keypwd);
			String alias;
			alias = ks.aliases().nextElement();
			PrivateKey prikey = (PrivateKey) ks.getKey(alias, keypwd);
			fis2.close();
			return prikey;
		} catch (FileNotFoundException e) {
			throw new Exception("找不到私钥文件");
		} catch (NoSuchAlgorithmException e) {
			throw new Exception("无此算法");
		} catch (CertificateException e) {
			throw new Exception("证书加载异常");
		} catch (KeyStoreException e) {
			throw new Exception("证书加载异常");
		} catch (UnrecoverableKeyException e) {
			throw new Exception("无此算法");
		} catch (IOException e) {
			throw new Exception("文件IO异常");
		}// 加载证书

	}

	/**
	 * 获取公钥key
	 * 
	 * @param cerPath
	 * @return
	 * @throws Exception
	 */
	public PublicKey getPublicKeyFromCer(String cerPath) throws Exception {
		try {
			CertificateFactory certificate_factory = CertificateFactory.getInstance("X.509");

			FileInputStream file_inputstream = new FileInputStream(cerPath);
			X509Certificate x509certificate = (X509Certificate) certificate_factory
					.generateCertificate(file_inputstream);
			PublicKey pk = x509certificate.getPublicKey();
			return pk;
		} catch (CertificateException e) {
			throw new Exception("证书加载异常");
		} catch (FileNotFoundException e) {
			throw new Exception("找不到公钥文件");
		}
	}

//	public static String SHA1(String decript) throws Exception {
//		try {
//			MessageDigest digest = java.security.MessageDigest.getInstance("SHA-1");
//			digest.update(decript.getBytes());
//			byte messageDigest[] = digest.digest();
//			// Create Hex String
//			StringBuffer hexString = new StringBuffer();
//			// 字节数组转换为 十六进制 数
//			for (int i = 0; i < messageDigest.length; i++) {
//				String shaHex = Integer.toHexString(messageDigest[i] & 0xFF);
//				if (shaHex.length() < 2) {
//					hexString.append(0);
//				}
//				hexString.append(shaHex);
//			}
//			return hexString.toString();
//
//		} catch (NoSuchAlgorithmException e) {
//			throw new Exception("签名加密失败");
//		}
//	}

}
